package com.thread.lock;

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.LockSupport;

/**
 * @author wangyingjie
 * @date
 * @remark 自定义可重入锁
 */
public class NeteaseLock implements Lock {

    //需要一个标识，来判断谁拿着锁(thread是谁，即谁拥有锁)
    AtomicReference<Thread> owner = new AtomicReference<>();
    //存储等待线程的信息
    LinkedBlockingQueue<Thread> waiters = new LinkedBlockingQueue<>();

    /**
     * 尝试加锁
     * @return
     */
    @Override
    public boolean tryLock() {
        //当前标识是否为空，如果是空则置为当前线程对象
        return owner.compareAndSet(null,Thread.currentThread());
    }

    /**
     * 加锁
     */
    @Override
    public void lock() {
        Boolean flag = false;//多循环一次，保证等待线程加到队列里了再挂起线程。否则，如果直接挂起，可能还没挂起，先被其他线程抢占资源，执行unlock(),然后再执行park()，线程就会一直挂在那里。
        while (!tryLock()){
            if (!flag){
                //尝试加锁失败，说明已经有线程拿锁，那么要等待
                //第一次循环，获取锁失败，加入到阻塞队列
                waiters.offer(Thread.currentThread());
                flag = true;
            }else {
                //第二次循环进来，因为true进来，然后挂起这个线程，阻塞(等待)
                LockSupport.park();//线程阻塞，原子操作，可以避免wait()和notify()的线程无线等待问题
            }
        }
        //拿到锁的情况，从等待集合中删除
        waiters.remove(Thread.currentThread());
    }

    /**
     * 解锁
     */
    @Override
    public void unlock() {
        if (owner.compareAndSet(Thread.currentThread(),null)){
            //当前线程拿着锁，才能解锁，把标识置为空，通知其他线程可以抢锁，放开阻塞
            Thread next = null;
            while ((next = waiters.peek())!=null){
                //唤醒阻塞的所有线程，去争取锁
                LockSupport.unpark(next);
            }
        }
    }


    @Override
    public void lockInterruptibly() throws InterruptedException {

    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return false;
    }

    @Override
    public Condition newCondition() {
        return null;
    }
}
